home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 8: LINUX Games
/
Linux Cubed Series 8 - LINUX Games.iso
/
games
/
x11
/
rpg
/
crossfir.92
/
crossfir
/
crossfire-0.92.5
/
server
/
alchemy.c
next >
Wrap
C/C++ Source or Header
|
1996-07-24
|
18KB
|
552 lines
/* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
#include <global.h>
#include <object.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
#include <skills.h>
#include <spells.h>
/* define this for some helpful debuging information */
#define ALCHEMY_DEBUG
/* define this for loads of (marginal) debuging information */
#define EXTREME_ALCHEMY_DEBUG
static char *cauldron_effect [] = {
"vibrates briefly",
"produces a cloud of steam",
"emits bright flames",
"pours forth heavy black smoke",
"emits sparks",
"shoots out small flames",
"whines painfully",
"hiccups loudly",
"wheezes",
"burps",
"shakes",
"rattles",
"makes chugging sounds",
"smokes heavily for a while"
};
/* cauldron_sound() - returns a random selection from cauldron_effect[] */
char * cauldron_sound ( void ) {
int size=sizeof(cauldron_effect)/sizeof(char *);
return cauldron_effect[RANDOM()%size];
}
/* attempt_do_alchemy() - Main part of the ALCHEMY code. From this we call fctns
* that take a look at the contents of the 'cauldron' and, using these ingredients,
* we construct an integer formula value which is referenced (randomly) against a
* formula list (the formula list chosen is based on the # contents of the cauldron).
*
* If we get a match between the recipe indicated in cauldron contents and a
* randomly chosen one, an item is created and experience awarded. Otherwise
* various failure effects are possible (getting worse and worse w/ # cauldron
* ingredients). Note that the 'item' to be made can be *anything* listed on
* the artifacts list in lib/artifacts which has a recipe listed in lib/formulae.
*
* To those wondering why I am using the funky formula index method:
* 1) I want to match recipe to ingredients regardless of ordering.
* 2) I want a fast search for the 'right' recipe.
*
* Note: it is just possible that a totally different combination of
* ingredients will result in a match with a given recipe. This is not a bug!
* There is no good reason (in my mind) why alchemical processes have to be
* unique -- such a 'feature' is one reason why players might want to experiment
* around. :)
* -b.t.
*/
void attempt_do_alchemy(object *caster, object *cauldron) {
recipelist *fl;
recipe *rp=NULL;
int numb,i;
int formula=0,danger=calc_alch_danger(caster,cauldron);
if(caster->type!=PLAYER) return; /* only players for now */
/* if no ingredients, no formula! lets forget it */
if(!(formula=content_recipe_value(cauldron))) return;
numb=numb_ob_inside(cauldron);
if((fl=get_formulalist(numb))) {
int j,ability=SK_level(caster)+cauldron->magic;
#ifdef ALCHEMY_DEBUG
LOG(llevDebug,"Got alchemy ability: %d\n",ability);
#endif
if(QUERY_FLAG(caster,FLAG_WIZ)) {
rp=fl->items;
while(rp && rp->index!=formula) {
#ifdef EXTREME_ALCHEMY_DEBUG
LOG(llevDebug," grabbed list %d formula: %s of %s (%d)\n",numb,
rp->arch_name,rp->title,rp->index);
#endif
rp=rp->next;
}
if(rp) {
#ifdef ALCHEMY_DEBUG
if(strcmp(rp->title,"NONE"))
LOG(llevDebug,"WIZ got formula: %s of %s\n",
rp->arch_name,rp->title);
else
LOG(llevDebug,"WIZ got formula: %s\n",rp->arch_name);
#endif
attempt_recipe(caster,cauldron,rp);
} else LOG(llevDebug,"WIZ couldnt find formula for ingredients.\n");
return;
}
/* player loop--trying to match random recipe */
for(i=ability;i>0;i--) {
rp=fl->items;
if(fl->number>1) /* select a random recipe */
for(j=RANDOM()%fl->number;j>1;j--) rp=rp->next;
if(rp->index==formula) { /* we matched a recipe */
if(!(attempt_recipe(caster,cauldron,rp)))
break;
else return;
}
}
}
/* if we get here, we failed!! */
alchemy_failure_effect(caster,cauldron,rp,danger);
}
/* content_recipe_value()- recipe value of the entire contents of a container. */
int content_recipe_value (object *op) {
char name[MAX_BUF];
object *tmp=op->inv;
int tval=0,formula=0;
while(tmp) {
tval=0;
strcpy(name,tmp->name);
if(tmp->title) sprintf(name,"%s %s",tmp->name,tmp->title);
tval = (strtoint(name) * (tmp->nrof?tmp->nrof:1));
#ifdef ALCHEMY_DEBUG
LOG(llevDebug,"Got ingredient %d %s(%d)\n",tmp->nrof?tmp->nrof:1,
name,tval);
#endif
formula += tval;
tmp=tmp->below;
}
#ifdef ALCHEMY_DEBUG
LOG(llevDebug," FORMULA value=%d\n",formula);
#endif
return formula;
}
/* numb_ob_inside() */
int numb_ob_inside (object *op) {
object *tmp=op->inv;
int number=0,o_number=0;
while(tmp) {
if(tmp->nrof) number += tmp->nrof;
else number++;
o_number++;
tmp=tmp->below;
}
#ifdef ALCHEMY_DEBUG
LOG(llevDebug,"numb_ob_inside(%s): found %d ingredients\n",op->name,o_number);
#endif
return o_number;
}
/* attempt_recipe() - essentially a wrapper for make_item_from_recipe() and
* insert_ob_in_ob. If the caster has some alchemy skill, then they might
* gain some exp from (successfull) fabrication of the product.
*/
object * attempt_recipe(object *caster, object *cauldron, recipe *rp) {
object *item=NULL;
if((item=make_item_from_recipe(cauldron,rp))!=NULL) {
remove_contents(cauldron->inv,item);
adjust_product(item,caster); /* adj lvl, nrof on caster level */
if(!item->env && (item=insert_ob_in_ob(item,cauldron))==NULL) {
new_draw_info_format(NDI_UNIQUE, 0,caster,
"Your spell causes the %s to explode!",cauldron->name);
/* kaboom_cauldron(); */
} else {
if(find_skill(caster,SK_ALCHEMY)!=NULL) {
change_skill(caster,SK_ALCHEMY);
add_exp(caster,calc_skill_exp(caster,item));
item->stats.exp=0; /* so when skill id, less xp awarded */
}
new_draw_info_format(NDI_UNIQUE, 0,caster,
"The %s %s.",cauldron->name,cauldron_sound());
}
}
return item;
}
/* table of alchemical probability (1-100). Used by adjust_product. */
static int alprob [] = { 10, 25, 35, 45, 50, 55, 65, 68, 70, 73, 75, 76, 77, 78, 79};
/* adjust_product() - we adjust the nrof, exp and level of the final product, based
* on the item's default parameters, and the relevant caster skill level. */
void adjust_product(object *item,object *caster) {
object *skill=NULL;
int lvl=1,nrof=1,maxnrof;
/* if the caster has alchemy (mental) exp use that level */
if((skill=find_skill(caster,SK_ALCHEMY)) && skill->exp_obj)
lvl = skill->exp_obj->level;
maxnrof = lvl/5;
if(item->nrof) {
int prob = maxnrof>(sizeof(alprob)/sizeof(int)) ? 80 : alprob[maxnrof];
while (maxnrof--)
if(RANDOM()%100<prob) nrof++;
item->nrof=nrof;
}
/* item exp. This will be used later for experience calculation */
item->stats.exp += lvl*lvl*nrof;
/* item->level = (lvl+item->level)/2; avg between default and caster levels */
}
/* make_item_from_recipe()- using a list of items and a recipe to make an artifact. */
object * make_item_from_recipe(object *cauldron, recipe *rp) {
artifact *art=NULL;
object *item=NULL;
if(rp==NULL) return (object *) NULL;
/* Find the appropriate object to transform...*/
if((item=find_transmution_ob(cauldron->inv,rp))==NULL) {
LOG(llevDebug,"make_alchemy_item(): failed to create alchemical object.\n");
return (object *) NULL;
}
/* Find the appropriate artifact template...*/
if(strcmp(rp->title,"NONE")) {
if((art=locate_recipe_artifact(rp))==NULL) {
LOG(llevError,"make_alchemy_item(): failed to locate recipe artifact.\n");
LOG(llevDebug," --requested recipe: %s of %s.\n",rp->arch_name,rp->title);
return (object *) NULL;
}
give_artifact_abilities(item,art->item);
}
if(QUERY_FLAG(cauldron,FLAG_CURSED)) SET_FLAG(item,FLAG_CURSED);
if(QUERY_FLAG(cauldron,FLAG_DAMNED)) SET_FLAG(item,FLAG_DAMNED);
return item;
}
/* find_transmution_ob() - looks through the ingredient list, if we find a
* suitable object in it - we will use that to make the requested artifact.
* Otherwise the code returns a 'generic' item. -b.t.
*/
object * find_transmution_ob ( object *first_ingred, recipe *rp) {
object *item=NULL;
if(rp->transmute) /* look for matching ingredient/prod archs */
for(item=first_ingred;item;item=item->below)
if(!strcmp(item->arch->name,rp->arch_name)) break;
/* failed, create a fresh object. Note no nrof>1 because that would
* allow players to create massive amounts of artifacts easily */
if(!item||item->nrof>1)
item=get_archetype(rp->arch_name);
#ifdef ALCHEMY_DEBUG
LOG(llevDebug,"recipe calls for%stransmution.\n",rp->transmute?" ":" no ");
if(strcmp(item->arch->name,rp->arch_name))
LOG(llevDebug,"WARNING: recipe calls for arch: %s\n",rp->arch_name);
LOG(llevDebug," find_transmutable_ob(): returns arch %s(sp:%d)\n",
item->arch->name,item->stats.sp);
#endif
return item;
}
/* alchemy_failure_effect - Ouch. We didnt get the formula we wanted.
* This fctn simulates the backfire effects--worse effects as the level
* increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
* can happen to the would be alchemist. This table probably needs some
* adjustment for playbalance. -b.t.
*/
void alchemy_failure_effect(object *op,object *cauldron,recipe *rp,int danger) {
int level=0;
if(!op || !cauldron) return;
if(danger>1) level=RANDOM()%danger+1;
#ifdef ALCHEMY_DEBUG
LOG(llevDebug,"Alchemy_failure_effect(): using level=%d\n",level);
#endif
/* possible outcomes based on level */
if(level>10&&level<25) { /* INGREDIENTS USED/SLAGGED */
object *item=NULL;
if(RANDOM()%3) { /* slag created */
object *tmp=cauldron->inv;
int weight=0;
uint16 material=M_STONE;
while(tmp) { /* slag has coadded ingredient properties */
weight+=tmp->weight;
if(!(material&tmp->material))
material=material|tmp->material;
tmp=tmp->below;
}
tmp = get_archetype("rock");
tmp->weight=weight;
tmp->value=0;
tmp->material=material;
free_string(tmp->name);
tmp->name=add_string("slag");
item=insert_ob_in_ob(tmp,cauldron);
CLEAR_FLAG(tmp,FLAG_CAN_ROLL);
CLEAR_FLAG(tmp,FLAG_NO_PICK);
CLEAR_FLAG(tmp,FLAG_NO_PASS);
}
remove_contents(cauldron->inv,item);
new_draw_info_format(NDI_UNIQUE,0,op,
"The %s %s.",cauldron->name,cauldron_sound());
return;
} else if (level< 40) { /* MAKE TAINTED ITEM */
object *tmp=NULL;
if (!rp)
if((rp=get_random_recipe((recipelist *) NULL))==NULL)
return;
if((tmp=attempt_recipe(op,cauldron,rp))) {
if(!QUERY_FLAG(tmp,FLAG_CURSED)) /* curse it */
SET_FLAG(tmp,FLAG_CURSED);
/* special stuff for consumables */
if(tmp->type==POTION||tmp->type==FOOD) {
if(tmp->stats.sp&&RANDOM()%2) /* drains magic */
tmp->stats.sp = SP_REGENERATE_SPELLPOINTS;
if(RANDOM()%2) { /* poisonous */
tmp->type=FOOD;
tmp->stats.hp=RANDOM()%150;
}
}
/* change stats downward */
if(tmp->type!=FOOD)
do {
change_attr_value(&tmp->stats,RANDOM()%6,-1*RANDOM()%3);
} while (RANDOM()%3);
}
return;
} if(level==40) { /* MAKE RANDOM RECIPE */
recipelist *fl;
int numb=numb_ob_inside(cauldron);
fl=get_formulalist(numb);
if(fl &&(rp=get_random_recipe(fl)))
(void) attempt_recipe(op,cauldron,rp);
else
alchemy_failure_effect(op,cauldron,rp,level-1);
return;
} else if (level<45) { /* INFURIATE NPC's */
/* this is kind of kludgy I know...*/
cauldron->enemy=op;
npc_call_help(cauldron);
cauldron->enemy=NULL;
alchemy_failure_effect(op,cauldron,rp,level-5);
return;
}
/* #ifdef SPELL_FAILURE_EFFECTS */
else if (level<50) { /* MINOR EXPLOSION/FIREBALL */
object *tmp;
remove_contents(cauldron->inv,NULL);
switch(RANDOM()%3) {
case 0:
tmp=get_archetype("bomb");
tmp->stats.dam=RANDOM()%level+1;
tmp->stats.hp=RANDOM()%level+1;
break;
new_draw_info_format(NDI_UNIQUE,0,op,"The %s creates a bomb!",
cauldron->name);
break;
default:
tmp=get_archetype("fireball");
tmp->stats.dam=(RANDOM()%level+1)/5+1;
tmp->stats.hp=(RANDOM()%level+1)/10+2;
new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
cauldron->name);
break;
}
tmp->x=cauldron->x,tmp->y=cauldron->y;
insert_ob_in_map(tmp,op->map);
return;
} else if (level<60) { /* CREATE MONSTER */
new_draw_info_format(NDI_UNIQUE,0,op,
"The %s %s.",cauldron->name,cauldron_sound());
remove_contents(cauldron->inv,NULL);
return;
} else if (level<80) { /* MAJOR FIRE */
remove_contents(cauldron->inv,NULL);
fire_arch (cauldron, 0, spellarch[SP_L_FIREBALL], SP_L_FIREBALL, 0);
new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
cauldron->name);
return;
}
/* #endif SPELL_FAILURE_EFFECTS */
else if (level<100) { /* WHAMMY the CAULDRON */
if(!QUERY_FLAG(cauldron,FLAG_CURSED))
SET_FLAG(cauldron,FLAG_CURSED);
else cauldron->magic--;
cauldron->magic -= RANDOM()%5;
if(RANDOM()%2) {
remove_contents(cauldron->inv,NULL);
new_draw_info_format(NDI_UNIQUE,0,op,
"Your %s turns darker then makes a gulping sound!",
cauldron->name);
} else
new_draw_info_format(NDI_UNIQUE,0,op,
"Your %s becomes darker.",cauldron->name);
return;
}
/* #ifdef SPELL_FAILURE_EFFECTS */
else if (level<110) { /* SUMMON EVIL MONSTERS */
object *tmp=get_random_mon(level/5);
remove_contents(cauldron->inv,NULL);
if(!tmp)
alchemy_failure_effect(op,cauldron,rp,level);
else if(summon_hostile_monsters(cauldron,RANDOM()%10+1,tmp->arch->name))
new_draw_info_format(NDI_UNIQUE, 0,op,
"The %s %s and then pours forth monsters!",
cauldron->name,cauldron_sound());
return;
} else if (level<150) { /* COMBO EFFECT */
int roll = RANDOM()%3+1;
while(roll) {
alchemy_failure_effect(op,cauldron,rp,level-39);
roll--;
}
return;
}
/* #endif SPELL_FAILURE_EFFECTS */
else if (level==151) { /* CREATE RANDOM ARTIFACT */
object *tmp;
/* this is meant to be better than prior possiblity,
* in this one, we allow *any* valid alchemy artifact
* to be made (rather than only those on the given
* formulalist) */
if(!rp) rp=get_random_recipe((recipelist *) NULL);
if(rp && (tmp=get_archetype(rp->arch_name))) {
generate_artifact(tmp,RANDOM()%((op->level/2)+2)+2);
if((tmp=insert_ob_in_ob(tmp,cauldron))) {
remove_contents(cauldron->inv,tmp);
new_draw_info_format(NDI_UNIQUE, 0,op,
"The %s %s.",cauldron->name,cauldron_sound());
}
}
return;
}
/* #ifdef SPELL_FAILURE_EFFECTS */
else if (level<200) { /* MANA STORM - watch out!! */
new_draw_info(NDI_UNIQUE,0,op,"You unwisely release potent forces!");
remove_contents (cauldron->inv,NULL);
cast_mana_storm(op,level);
return;
}
/* #endif SPELL_FAILURE_EFFECTS */
}
/* remove_contents() - all but object "save_item" are elimentated from
* the container list. Note we have to becareful to remove the inventories
* of objects in the cauldron inventory (ex icecube has stuff in it).
*/
void remove_contents (object *first_ob, object *save_item) {
object *next,*tmp=first_ob;
while(tmp) {
next = tmp->below;
if(tmp==save_item)
if(!(tmp=next)) break;
else next=next->below;
if(tmp->inv) remove_contents(tmp->inv,NULL);
remove_ob(tmp);
free_object(tmp);
tmp=next;
}
}
/* calc_alch_danger() - "Danger" level will determine how bad the backfire
* could be if the user fails to concoct a recipe properly. Factors include
* the number of ingredients, the length of the name of each ingredient,
* the user's effective level, the user's Int and the enchantment on the
* mixing device (aka "cauldron"). Higher values of 'danger' indicate more
* danger. -b.t.
*/
int calc_alch_danger(object *caster,object *cauldron) {
object *item;
char name[MAX_BUF];
int danger=0,nrofi=0;
/* Higher level, better cauldrons reduce risk */
danger -= (SK_level(caster)+(cauldron->magic));
/* Higher Int, lower the risk */
danger -= 3 * (caster->stats.Int - 15);
/* Ingredients. Longer names usually mean rarer stuff.
* Thus the backfire is worse. Also, more ingredients
* means we are attempting a more powerfull potion,
* and thus the backfire will be worse. */
for(item=cauldron->inv;item;item=item->below) {
strcpy(name,item->name);
if(item->title) sprintf(name,"%s %s",item->name,item->title);
danger += (strtoint(name)/1000) + 3;
nrofi++;
}
if(nrofi>1) danger *= nrofi;
/* Using a bad device is *majorly* stupid */
if(QUERY_FLAG(cauldron,FLAG_CURSED)) danger +=80;
if(QUERY_FLAG(cauldron,FLAG_DAMNED)) danger +=200;
LOG(llevDebug,"calc_alch_danger() returned danger=%d\n",danger);
return danger;
}